/**
 * 
 */
package neptune.web.server.repository;

import it.robertobifulco.ias.RepositoryException;
import it.robertobifulco.ias.principalmanager.ImpossibleToIdentifyPrincipalException;
import it.robertobifulco.ias.principalmanager.PrincipalRepository;

import java.io.File;
import java.io.FileWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import neptune.web.server.Debug;
import neptune.web.server.model.ApplicationUser;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
 * 
 */
public class XMLUsersRepository implements PrincipalRepository,
		ApplicationRepository {

	private File xmlFile;

	public static int USERNAME = 0;
	public static int PASSWORD = 1;
	public static int ROLE = 2;
	public static int EMAIL = 3;

	public XMLUsersRepository(File xmlFile) {
		super();
		this.xmlFile = xmlFile;

		if (!xmlFile.exists())
			try {
				writeRepositoryFile(new ArrayList<String[]>(0));
			} catch (RepositoryException e) {
				throw new RuntimeException(e);
			}
	}

	public synchronized ArrayList<String[]> readRepositoryFile()
			throws RepositoryException {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setValidating(false);

		try {
			Document doc = docFactory.newDocumentBuilder().parse(xmlFile);

			ArrayList<String[]> users = new ArrayList<String[]>(0);

			Element usersEl = doc.getDocumentElement();
			NodeList userEls = usersEl.getChildNodes();

			for (int i = 0; i < userEls.getLength(); i++) {
				if (!(userEls.item(i) instanceof Element))
					continue;

				Element userEl = (Element) userEls.item(i);
				String[] userData = new String[4];
				userData[USERNAME] = userEl.getAttribute("id");
				userData[PASSWORD] = userEl.getAttribute("password");
				userData[ROLE] = userEl.getAttribute("role");
				userData[EMAIL] = userEl.getAttribute("email");
				users.add(userData);
			}

			return users;
		} catch (Exception e) {
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
	}

	public synchronized void writeRepositoryFile(ArrayList<String[]> users)
			throws RepositoryException {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setValidating(false);

		try {
			Document document = docFactory.newDocumentBuilder().newDocument();
			document.setXmlVersion("1.0");

			Element root = document.createElement("users");
			document.appendChild(root);

			for (String[] userData : users) {
				Element user = document.createElement("user");
				user.setAttribute("id", userData[USERNAME]);
				user.setAttribute("password", userData[PASSWORD]);
				user.setAttribute("role", userData[ROLE]);
				user.setAttribute("email", userData[EMAIL]);
				root.appendChild(user);
			}

			// ///////////////
			// Output the XML

			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			trans.setOutputProperty(OutputKeys.STANDALONE, "yes");
			trans.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");

			// create string from xml tree
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(document);
			trans.transform(source, result);
			String xmlString = sw.toString();

			xmlFile.createNewFile();
			FileWriter writer = new FileWriter(xmlFile);
			writer.write(xmlString);
			writer.flush();

		} catch (Exception e) {
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seeit.robertobifulco.ias.principalmanager.PrincipalRepository#
	 * getPrincipalPassword(java.lang.String)
	 */
	public String getPrincipalPassword(String principalID)
			throws ImpossibleToIdentifyPrincipalException {
		try {
			for (String[] user : readRepositoryFile()) {
				if (user[USERNAME].equals(principalID))
					return user[PASSWORD];
			}

			throw new ImpossibleToIdentifyPrincipalException("Principal '"
					+ principalID + "' is undefined.");
		} catch (RepositoryException e) {
			Debug.printTrace(e);
			throw new ImpossibleToIdentifyPrincipalException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.robertobifulco.ias.principalmanager.PrincipalRepository#getPrincipalRole
	 * (java.lang.String)
	 */
	public String getPrincipalRole(String principalID)
			throws ImpossibleToIdentifyPrincipalException {
		try {
			for (String[] user : readRepositoryFile()) {
				if (user[USERNAME].equals(principalID))
					return user[ROLE];
			}

			throw new ImpossibleToIdentifyPrincipalException("Principal '"
					+ principalID + "' is undefined.");
		} catch (RepositoryException e) {
			Debug.printTrace(e);
			throw new ImpossibleToIdentifyPrincipalException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.web.server.repository.ApplicationRepository#addUser(neptune.web
	 * .server.model.ApplicationUser)
	 */
	public void addUser(ApplicationUser user) throws RepositoryException {
		try {

			boolean userExists = false;
			try {
				if (getUserByName(user.getUsername()) != null)
					userExists = true;
			} catch (Exception e) {
				// do nothing if the user doesn't exists.
			}

			if (userExists)
				throw new RepositoryException("User already exists");

			ArrayList<String[]> users = readRepositoryFile();

			users.add(new String[] { user.getId(), user.getPassword(),
					user.getRole(), user.getEmail() });

			writeRepositoryFile(users);
		} catch (RepositoryException e) {
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.web.server.repository.ApplicationRepository#getUserByName(java
	 * .lang.String)
	 */
	public ApplicationUser getUserByName(String username)
			throws RepositoryException {
		try {
			for (String[] user : readRepositoryFile()) {
				if (user[USERNAME].equals(username)) {
					ApplicationUser appUser = new ApplicationUser(false,
							user[ROLE], user[USERNAME]);
					appUser.setEmail(user[EMAIL]);
					appUser.setPassword(user[PASSWORD]);

					return appUser;
				}
			}

			throw new RepositoryException("User '" + username
					+ "' is undefined.");
		} catch (RepositoryException e) {
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see neptune.web.server.repository.ApplicationRepository#getUsers(int,
	 * int, int, boolean)
	 */
	public ApplicationUser[] getUsers(int begin, int end, int sortId,
			boolean sortAscending) throws RepositoryException {
		try {
			ArrayList<String[]> users = readRepositoryFile();
			String[][] usersArray = users.toArray(new String[][] {});

			final int sortCell;
			switch (sortId) {
			case 0:
				sortCell = USERNAME;
				break;
			case 1:
				sortCell = ROLE;
				break;
			case 2:
				sortCell = EMAIL;
				break;
			default:
				sortCell = USERNAME;
				break;
			}

			final int sign = sortAscending ? 1 : -1;

			Arrays.sort(usersArray, new Comparator<String[]>() {
				public int compare(String[] o1, String[] o2) {
					return sign * o1[sortCell].compareTo(o2[sortCell]);
				}
			});

			if (begin > users.size())
				begin = users.size();
			if (end > users.size())
				end = users.size();
			if (begin > end)
				begin = end;

			String[][] subArray = Arrays.copyOfRange(usersArray, begin, end);

			ApplicationUser appUsers[] = new ApplicationUser[subArray.length];
			int i = 0;
			for (String[] user : subArray) {
				appUsers[i] = new ApplicationUser(false, user[ROLE],
						user[USERNAME]);
				appUsers[i].setEmail(user[EMAIL]);
				appUsers[i].setPassword(user[PASSWORD]);
				i++;
			}

			return appUsers;
		} catch (RepositoryException e) {
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see neptune.web.server.repository.ApplicationRepository#getUsersNumber()
	 */
	public int getUsersNumber() throws RepositoryException {
		return readRepositoryFile().size();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.web.server.repository.ApplicationRepository#removeUser(neptune
	 * .web.server.model.ApplicationUser)
	 */
	public void removeUser(ApplicationUser user) throws RepositoryException {
		removeUserByName(user.getId());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.web.server.repository.ApplicationRepository#removeUserByName(
	 * java.lang.String)
	 */
	public boolean removeUserByName(String username) throws RepositoryException 
	{
		boolean isRemoved=false;
		try 
		{
			ArrayList<String[]> users = readRepositoryFile();

			int index = -1;
			for (String[] actual : users) 
			{
				if (actual[USERNAME].equals(username))
					index = users.indexOf(actual);
			}
			if (index != -1)
			{
				users.remove(index);
				writeRepositoryFile(users);
				isRemoved=true;
			}
		}
		catch (RepositoryException e) 
		{
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
		return isRemoved;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.web.server.repository.ApplicationRepository#updateUser(neptune
	 * .web.server.model.ApplicationUser)
	 */
	public void updateUser(ApplicationUser user) throws RepositoryException {
		try {
			ArrayList<String[]> users = readRepositoryFile();

			for (String[] actual : users) {
				if (actual[USERNAME].equals(user.getId())) {
					users.remove(actual);
					break;
				}
			}

			//Adds the modified/new user
			String[] actual = new String[] { user.getId(), user.getPassword(),
					user.getRole(), user.getEmail() };
			users.add(actual);
			
			writeRepositoryFile(users);
		} catch (RepositoryException e) {
			Debug.printTrace(e);
			throw new RepositoryException(e);
		}
	}

}
